Skip to Content

一、 什么是面向对象编程?(核心思想)

想象一下,在面向对象出现之前,我们编程的方式(比如C语言的过程式编程)就像是写一份详细的菜谱:

  1. 准备食材A。
  2. 切食材A。
  3. 准备食材B。
  4. 煮食材B。

这种方式将数据(食材)和操作(切、煮)分离开来。当程序变得复杂时,管理这些分离的数据和操作会变得非常困难。

面向对象编程则提出了一种全新的思维方式:将现实世界的事物抽象成程序中的“对象”(Object)

一个对象,就像一个现实中的实体,它有自己的:

  • 属性(Attributes):描述这个对象的状态和特征的数据。例如,一个“汽车”对象有颜色、品牌、速度等属性。
  • 行为(Behaviors):描述这个对象能做什么的方法或函数。例如,“汽车”对象可以加速、刹车、鸣笛。

核心思想:将**数据(属性)操作数据的函数(行为)**捆绑在一起,形成一个独立的、可复用的单元——对象。程序就是由无数个这样的对象相互协作、通信来完成任务的。


二、 面向对象的四大支柱

C++ 的面向对象特性主要建立在四个核心概念之上,它们通常被称为“四大支柱”。

1. 封装 (Encapsulation)

概念:封装是将数据(属性)和操作数据的代码(方法)捆绑到一个独立的单元(即 class)中,并对对象的内部细节进行隐藏(信息隐藏)。外部世界只能通过对象暴露出来的公共接口(public methods)来访问或修改其数据。

好比:一台自动售货机。你不需要知道它内部的制冷原理、机械结构,你只需要通过它提供的按钮(公共接口)来选择商品并付款,机器就会自动完成内部操作并吐出商品。

C++ 实现

  • 使用 classstruct 关键字定义一个类。
  • 使用访问修饰符 public, private, protected 来控制成员的可见性。
    • public:任何地方都可以访问。是类的“公共接口”。
    • private:只有类的内部成员函数可以访问。这是实现“信息隐藏”的关键。
    • protected:类的内部和其子类可以访问。

代码示例

#include <iostream> #include <string> class Car { private: // 私有成员,外部无法直接访问 std::string color; int speed; public: // 公共接口 // 构造函数,用于初始化对象 Car(std::string c, int s) : color(c), speed(s) { std::cout << "一辆" << color << "的车被制造出来了!" << std::endl; } void accelerate(int increment) { if (increment > 0) { speed += increment; std::cout << "加速!当前速度: " << speed << " km/h" << std::endl; } } void brake(int decrement) { if (decrement > 0) { speed -= decrement; if (speed < 0) speed = 0; std::cout << "刹车!当前速度: " << speed << " km/h" << std::endl; } } int getCurrentSpeed() const { // const 表示该函数不会修改成员变量 return speed; } }; int main() { Car myCar("红色", 0); // 创建一个Car对象 // myCar.speed = 100; // 错误!speed是私有的,无法直接访问 myCar.accelerate(50); myCar.brake(20); std::cout << "通过接口获取当前速度: " << myCar.getCurrentSpeed() << " km/h" << std::endl; return 0; }

2. 继承 (Inheritance)

概念:继承允许我们创建一个新类(派生类子类),这个新类可以继承一个已存在的类(基类父类)的属性和方法。这促进了代码复用,并建立起类之间的 “is-a” (是一个) 的层次关系。

好比:“轿车”是一个“汽车”,“卡车”也是一个“汽车”。“轿车”和“卡车”都继承了“汽车”的基本属性(如轮子、发动机)和行为(如行驶、停止),但它们各自还有独特的特性(轿车有座位数,卡车有载重量)。

C++ 实现

  • 使用 : 语法来表示继承关系:class DerivedClass : public BaseClass { ... };

代码示例

// 假设我们已经有了上面的 Car 类作为基类 class Truck : public Car { // Truck 继承自 Car private: double payloadCapacity; // 载重量,Truck特有的属性 public: // 子类的构造函数需要调用父类的构造函数 Truck(std::string c, int s, double payload) : Car(c, s), payloadCapacity(payload) { std::cout << "它还是一辆卡车,载重量为 " << payloadCapacity << " 吨。" << std::endl; } void loadGoods(double weight) { std::cout << "装载了 " << weight << " 吨货物。" << std::endl; } }; int main() { Truck myTruck("蓝色", 0, 10.5); myTruck.accelerate(30); // 调用从Car继承来的方法 myTruck.loadGoods(5.0); // 调用Truck自己的方法 return 0; }

3. 多态 (Polymorphism)

概念:多态的字面意思是“多种形态”。它允许我们使用一个统一的接口(通常是基类的指针或引用)来处理不同类型的对象,而程序会在运行时自动选择并调用相应对象的正确方法。

好比:你有一个遥控器(接口),它可以控制电视机、空调、DVD播放器。当你按下“开/关”按钮时,电视会打开屏幕,空调会开始吹风,DVD会开始读盘。同一个“开/关”指令,对于不同的设备,产生了不同的行为。

C++ 实现:多态主要通过虚函数 (Virtual Functions)动态绑定 (Dynamic Binding) 来实现。

  • 在基类中,将希望在派生类中被重写(override)的函数声明为 virtual
  • 通过基类的指针或引用来调用这个虚函数。

代码示例

#include <iostream> #include <vector> #include <memory> class Animal { public: // 声明一个虚函数 virtual void speak() const { std::cout << "动物发出声音..." << std::endl; } // 当基类有虚函数时,析构函数也应该是虚的,以确保正确释放资源 virtual ~Animal() {} }; class Dog : public Animal { public: // 使用 override 关键字明确表示这是重写基类的虚函数(好习惯) void speak() const override { std::cout << "汪汪!" << std::endl; } }; class Cat : public Animal { public: void speak() const override { std::cout << "喵喵!" << std::endl; } }; // 这个函数接受任何Animal类型的指针,并调用其speak方法 void makeAnimalSpeak(const Animal* animal) { animal->speak(); // 这里的调用在运行时决定! } int main() { Dog myDog; Cat myCat; Animal genericAnimal; makeAnimalSpeak(&myDog); // 传入Dog对象,输出 "汪汪!" makeAnimalSpeak(&myCat); // 传入Cat对象,输出 "喵喵!" makeAnimalSpeak(&genericAnimal); // 传入Animal对象,输出 "动物发出声音..." std::cout << "\n--- 使用容器演示多态 ---\n"; // 使用智能指针管理动态分配的对象 std::vector<std::unique_ptr<Animal>> zoo; zoo.push_back(std::make_unique<Dog>()); zoo.push_back(std::make_unique<Cat>()); zoo.push_back(std::make_unique<Dog>()); for (const auto& animal : zoo) { animal->speak(); // 同一个调用,根据对象的实际类型产生不同行为 } return 0; }

关键点makeAnimalSpeak 函数和 for 循环中的 animal->speak() 并不知道它具体操作的是 Dog 还是 Cat。它只知道这是一个 Animal。这种在运行时才确定调用哪个函数版本的机制,就是多态的精髓,它让我们的代码变得极具扩展性。

4. 抽象 (Abstraction)

概念:抽象是关注对象的本质特征,而忽略其不相关的细节。它是在更高层次上对现实世界的简化。抽象通常与封装和继承协同工作。

  • 封装隐藏了实现细节
  • 抽象暴露了本质接口

好比:当我们开车时,我们操作的是方向盘、油门和刹车(抽象接口)。我们不需要关心发动机如何点火、变速箱如何换挡(实现细节)。方向盘、油门、刹车就是对“驾驶”这一复杂行为的抽象。

C++ 实现

  • 抽象类 (Abstract Class):包含至少一个纯虚函数 (Pure Virtual Function) 的类。
  • 纯虚函数:一个没有实现的虚函数,其声明形式为 virtual void aFunction() = 0;
  • 抽象类不能被实例化(不能创建对象),它存在的唯一目的就是作为其他类的基类,为派生类定义一个必须实现的接口规范。

代码示例

// 将之前的 Animal 例子升级为抽象类 class Shape { // 这是一个抽象基类 public: // 纯虚函数,定义了一个接口:任何“形状”都必须能被绘制 virtual void draw() const = 0; virtual ~Shape() {} }; // 如果一个类继承了抽象类,但没有实现所有的纯虚函数,那么它自己也仍然是抽象类。 class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} void draw() const override { // 必须实现 draw 方法 std::cout << "绘制一个半径为 " << radius << " 的圆形。" << std::endl; } }; class Square : public Shape { private: double side; public: Square(double s) : side(s) {} void draw() const override { // 必须实现 draw 方法 std::cout << "绘制一个边长为 " << side << " 的正方形。" << std::endl; } }; int main() { // Shape myShape; // 错误!不能实例化抽象类 Shape std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Circle>(5.0)); shapes.push_back(std::make_unique<Square>(4.0)); for (const auto& shape : shapes) { shape->draw(); // 多态的应用:调用各自的draw实现 } return 0; }

在这个例子中,Shape 类通过纯虚函数 draw() 强制规定了所有派生类都必须提供一个绘制自己的方法,这就是一种强大的抽象。


三、 总结

支柱核心思想C++ 主要实现机制带来的好处
封装捆绑数据和方法,隐藏内部细节class, public, private, protected数据安全、模块化、降低耦合
继承基于现有类创建新类,代码复用: public/protected/private BaseClass代码重用、建立类层次结构
多态同一接口,多种实现,运行时绑定virtual 函数, 基类指针/引用灵活性、可扩展性、遵循“开闭原则”
抽象关注本质,忽略细节,定义规范抽象类, virtual ... = 0; (纯虚函数)定义清晰的接口、强制派生类实现功能

面向对象编程是C++的灵魂。它不仅仅是一套语法规则,更是一种强大的软件设计哲学。通过封装、继承和多态,我们可以构建出模块化、可复用、易于维护和扩展的复杂系统,这正是C++在大型项目(如游戏引擎、操作系统、金融交易系统)中备受青睐的重要原因。

Last updated on